Function New-FileOrFolder($p,[Switch]$d){
  if(!(test-path $p -ea 0)){if($d){new-item -ItemType Directory -path $p -Force -ea 0|out-null}else{new-item -ItemType File -path $p -Force -ea 0|out-null}}
}
Function Set-FileAccess(){ # this function is used to add given user access to the given path
    param(
    	[Parameter(Mandatory=$True)]     
    	[string]$path,                   # the given path to add access
    	[string]$user,                   # the given user to be added access to the given path
    	[Parameter(Mandatory=$False)]    
    	[string]$access="Modify"         # the access type to be added, default is "Modify"
    )
    Try{
        $Acl = (Get-Item $path).GetAccessControl('Access')
        $InheritanceFlags="None" ; $PropagationFlags="None"
        If( (Get-Item $path).psiscontainer){
            $InheritanceFlags="ObjectInherit"
        }
        $Ar = New-Object System.Security.AccessControl.FileSystemAccessRule($user, $access,$InheritanceFlags, $PropagationFlags, 'Allow')
        $Acl.SetAccessRule($Ar)
        Set-Acl -path $path -AclObject $Acl
    }Catch{
        
    }Finally{}
}
Function Validate-Credential(){ # check if the given fid credential is valid , so far unauthorized user is not in concern 
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [ValidatePattern("^(nam|apac|eur)\\.{7,9}")]
        [string]$fid,
        [string]$pwd,
        [Parameter(Mandatory=$False)]
        [string]$log_file="$home\Documents\WindowsPowerShell\log.txt"
    )
    $sec_pwd=ConvertTo-SecureString $pwd -AsPlainText -Force
    $creds = New-Object System.Management.Automation.PSCredential ($fid, $sec_pwd)
    try{
        start-process -Credential $creds -FilePath ping -ArgumentList "1>$null 2>&1" -WindowStyle Hidden -ea 1
        return $true
    } catch {
        write-log Error $_.exception $log_file ; return $false
    }
}
Function New-ScheduledTask(){
    #This function is used because when using SCHTASKS to create the task, the single quote  ' will always be forced transferred to be double quote "
    # paramters using
    # $task_arg="-WindowStyle Hidden -Executionpolicy unrestricted `"test.ps1 `'C:\Program Files (x86)\Java`'`""
    # -run_task_user 'System'                                                          ==> use system account to run this task
    # -run_task_user 'System' -runas                                                   ==> use system account to run this task with highest privileges
    # -run_task_user 'nam\aaadmin' $run_task_user_pwd 'xxxxxxx' -run_login_only        ==> use specified account to run this task
    # -run_task_user 'nam\aaadmin' $run_task_user_pwd 'xxxxxxx' -run_login_only -runas ==> use specified account to run this task with highest privileges
    # -task_arg "-noexit -Executionpolicy unrestricted `"c:\temp\test.ps1 `'C:\Program Files (x86)\Java`'`"" -run_task_user 'nam\cw50406' -run_login_only
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$False)]                  
        [string]$task_name='temp_task',                         # the task name to be created and invoked later
        [string]$task_executor= "powershell.exe",               # the main task file or exe to be ran
        [AllowNull()][string]$task_arg,                         # the arguments and parameters for the $task_command
        [string]$run_task_vdi="localhost",                      # the vdi to be ran the task
        [string]$run_task_user='System',                        # the user who will run the task, default is System account
        [object]$run_task_user_pwd=$null,                       # the password for user running, default is null for system or login running only tpye
        $log_file="$home\Documents\WindowsPowerShell\log.txt",  # the log file for log info store
        [switch]$run_login_only,                                # run task only when the user has logged in the VDI, must be true for non-system account
        [switch]$runas                                          # run task with highest privileges
    )
    #<--------------------------------- create and connect remote VDI Schedule Service
    Try{
        $task_schedule_service = new-object -ComObject("Schedule.Service")
        Try{
            $task_schedule_service.Connect($vdi)
            #<--------------------------------- setup task definition
            $task_definition = $task_schedule_service.NewTask(0) 
            $task_definition.Settings.Enabled = $true
            $task_definition.Settings.AllowDemandStart = $true
            #<--------------------------------- setup actual task actions
            $task_action = $task_definition.Actions.Create(0)
            $task_action.Path = $task_executor
            $task_action.Arguments = $task_arg
            #<--------------------------------- setup task trigger to be once ran 
            $task_triggers = $task_definition.Triggers
            $task_trigger = $task_triggers.Create(1)
            $task_trigger.StartBoundary = "2100-01-01T00:00:00"
            $task_trigger.Enabled = $true
            #<--------------------------------- register the task in remote VDI with given account(default is System account)
            Try{
                $root_folder = $task_schedule_service.GetFolder("\")
                $logon_type=Switch($run_login_only){$true{3;break}$false{5}}
                $root_folder.RegisterTaskDefinition($task_name,$task_definition,6,$run_task_user,$run_task_user_pwd,$logon_type) 1>$null
                If( $runas ){
                    $error.clear()
                    If( SCHTASKS /QUERY /S $run_task_vdi /TN $task_name 2>$null){ #<----------------------check if task has been created
                        $run_task_user_pwd|SCHTASKS /Change /S $run_task_vdi /RL HIGHEST /tn $task_name 1>$null 2>&1
                        #<----------------------check if task has been changed to be ran with highest privileges
                        If( (SCHTASKS /QUERY /TN temp_task /XML 2>$null) -imatch 'HighestAvailable'){
                            return $true
                        }else{
                            write-log Error $error $log_file ; return $false
                        }
                    }else{ write-log Error 'Not found task $task_name to set its running as admin' $log_file ; return $false}
                }else{return $true}
            }Catch{write-log Error $_ $log_file ; return $false}Finally{}
        }Catch{write-log Error $_ $log_file ; return $false}Finally{}
    }Catch{write-log Error $_ $log_file ; return $false}Finally{}
}
Function Get-ProcessUsage(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [int]$cpu_cores,
        [AllowNull()][Double]$max_cpu_usage,
        [AllowNull()][Double]$max_memory_usage,
        [Parameter(Mandatory=$False)]
        [string]$log_file
    )
    $current_cpu_usage=[math]::Round((((Get-Counter "\Process(powershell*)\% Processor Time" -ea 0).CounterSamples.CookedValue|Measure-Object -Sum).Sum / $cpu_cores), 2)
    $current_memory_usage = ((ps powershell*).WS|Measure-Object -Sum).Sum / 1kb
    write-log Normal "Powershell Current CPU Usage(%): $current_cpu_usage | Memory(KB): $current_memory_usage" $log_file
    If( $current_cpu_usage -gt $max_cpu_usage ){ $max_cpu_usage=$current_cpu_usage }
    If( $current_memory_usage -gt $max_memory_usage ){ $max_memory_usage=$current_memory_usage }
    return $max_cpu_usage,$max_memory_usage
}
Function Write-Log(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$true)]
        [ValidateSet("Warning", "Error", "Success","Normal")]
        [string]$log_type,
        [string]$log_info,
        [string]$log_file
    )
    $cnt=0
    Do{
        Try{"{0:yyyy-MM-dd HH:mm:ss}--{1}: {2}" -f (get-date),$log_type,$log_info |Out-File -FilePath $log_file -Append -Encoding utf8 -Force -ea 1;break}Catch{ sleep -Milliseconds 500 ;if(++$cnt -gt 10){write-error "wait too long(5s) to write log";break}
        }Finally{}
    }While(1)
}
Function Connect-Sql(){
    param(
        [Parameter(Mandatory=$True)]
        [string]$DBServer,
        [string]$DBPwd,
        [Parameter(Mandatory=$False)]
        [string]$log_file
    )
    $connection = New-Object System.Data.SqlClient.SqlConnection
    $connection.ConnectionString = "Server=$DBServer;uid=aa_dbread;pwd=$DBPwd;Database=ICGDB;Integrated Security=False;"
    Try{
       $connection.Open() ; return $connection
    }Catch{
       write-log Error $_ $log_file ; return $null
    }Finally{}
}
Function Execute-Sql(){
    param(
        [Parameter(Mandatory=$True)]
        $connection,
        [Parameter(Mandatory=$False)]
        [string]$query,
        [string]$log_file
    )
    Try{
        $adapter = New-Object System.Data.sqlclient.sqlDataAdapter (new-object system.data.sqlclient.sqlcommand($query,$connection))
        $dataset = New-Object System.Data.DataSet ; $adapter.Fill($dataSet) | Out-Null
        return $dataSet.Tables[0]
    }Catch{
        write-log Error $_.exception $log_file ; return $null
    }Finally{}
}
Function Start-BulkThreads(){	# -Action { Stop-Process -Id $PID } ----------------------> to be updated
	# useing parallel threads to run scripts in the same time so that it will speedup the running duration
	#reference https://www.codeproject.com/tips/895840/multi-threaded-powershell-cookbook
    param(
    	[Parameter(Mandatory=$True)]
    	[Object[]]$vdilist,             # the vdi list that scripts to be ran
    	[ScriptBlock]$Action,           # the actual action that each thread will run
    	[AllowNull()]
    	[Object[]]$ActionParams,        # the parameters that each thread needs, this can be a single or array of parameter
    	[Parameter(Mandatory=$false)]   
    	[int]$max_threads_count=100,    # the default amount of powershell threads, if this number is big, then the system might get stuck
        [string]$log_file="$home\Documents\WindowsPowerShell\log.txt"
    )
    $total_thread_return_result = New-Object System.Collections.ArrayList
	If( $vdilist.Count -eq 1 ){ # no need to create thread pool to run it
		If( [string]::IsNullOrWhiteSpace($vdilist[0]) ){
		    write-log Error "vdilist only contains 1 line but it's null" $log_file
		}else{
            Try{
                [void]$total_thread_return_result.Add((& $Action $vdilist[0] 1 $ActionParams))
            }Catch{
                write-log Error $_.Exception.Message $log_file
            }Finally{}
        }
	}else{
		If( $vdilist.Count -le $max_threads_count ){ $max_threads_count = $vdilist.Count }
		# creaate the thread pool and run each thread based the vdi list amount
		$SessionState = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
		$Pool = [runspacefactory]::CreateRunspacePool(1, $max_threads_count, $SessionState, $Host)
		$Pool.Open() ; $threads = New-Object System.Collections.ArrayList ; $cnt=1
        $handles = @()
		foreach($line in $vdilist){
			if( [string]::IsNullOrWhiteSpace($line ) ){continue}
            write-verbose "Starting to create thread $cnt with param <<$line>>" -verbose
            Try{
                $powershell = [powershell]::Create().AddScript($Action).AddArgument($line).AddArgument($cnt).AddArgument($ActionParams)
            }Catch{
                write-log Error $_.Exception.Message $log_file ; return $null
            }Finally{}
			$powershell.RunspacePool = $Pool ; $handles+=$powershell.BeginInvoke() ; [void]$threads.Add($powershell) ; $cnt++
		}
        $finish_cnt=0 ; $total_cnt=$handles.count
		do{ $done = $true # wait each thread to be done and then collect its return value
            $complete_percent=[math]::round(($finish_cnt / $total_cnt * 100),2)
            Write-Progress -Activity "Remaining $($total_cnt - $finish_cnt) of $total_cnt threads running" -PercentComplete $complete_percent -Status "$complete_percent completed" 
            For( $i=0; $i -lt $handles.count; $i++ ){
                if($handles[$i] -ne $null){
                    if($handles[$i].IsCompleted){
                        write-verbose ("The thread $($i+1) with param <<{0}>> has been completed" -f $vdilist[$i]) -verbose
                        If( ($single_thread_result=$threads[$i].EndInvoke($handles[$i])) -ne $null){ # return PSDataCollection<PSObject>
                            $single_thread_result.ReadAll()|%{[void]$total_thread_return_result.Add($_)}
                        }
                        $threads[$i].Dispose(); $threads[$i] = $null; $handles[$i]=$null ; $finish_cnt++
                    }else{$done = $false}
                }  
            }
            Start-Sleep -Milliseconds 200
		} until ($done)
        $threads=$null ; $handles=$null
        'threads','handles'|%{Remove-Variable $_ -Force -ea 0}
        [void]$Pool.close()  ; [void]$Pool.Dispose()
	}
    return $total_thread_return_result
}
Function Export-LogToAutosys(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$False)]
        [string]$log_file,
        [string]$autosys_log="C:\Program Files\CA\Workload Automation AE\SystemAgent\WA1_AGENT\log\VDICleanup\out.log"
    )
	Write-Log Normal "----------------------------END OF SCRIPT----------------------------`n" $log_file
	Try{
		get-content $log_file|Add-Content $autosys_log -Force -Encoding UTF8
	}Catch{
	    write-log Error $_ $log_file
	}Finally{}
}
Function Send-Mailtoteam( ){ # this function is used to send email to notify team that the script finished.
	[CmdletBinding()]param(
	    [Parameter(Mandatory=$True)]
	    [string]$script_run_result ,      # SUCCESS WARNING FAILURE
	    [string]$vdi_env ,                # for email subject use
	    [string[]]$attached_files         # attachedments to be sent to the team
	)
    Try{ # cw50406@imcnam.ssmb.com  dl.gt.cn.cstc.rpa.aa.ste@imcnam.ssmb.com
		Send-MailMessage `
            -From "$this_user.$this_machine@imcnam.ssmb.com" `
            -To dl.gt.cn.cstc.rpa.aa.ste@imcnam.ssmb.com `
            -Subject "[$vdi_env-$this_domain-VDI] RPA AA STE VDI Cleanup - $script_run_result" `
            -Body "Hi RPA AA STE Support, `n`nVDI Cleanup has been taken in $this_domain VDIs.`n`nPlease check details in the attached log. `n`nThank You! `n`nAAADMIN" `
            -Attachments $attached_files `
            -SmtpServer "imta.citicorp.com" -Port 25 `
            -Encoding([System.Text.Encoding]::UTF8) -ea 1
        Write-Log Success 'The notify email has been sent to RPA AA STE team.' $log_file
	}Catch{
	    Write-Log Error $_ $log_file
	}Finally{}
}
Function Stop-WithError(){ # this function is used to quit script when there is error with script running
    param(
		[Parameter(Mandatory=$True)]
    	[string]$error_detail,
    	[string]$vdi_env
    )
	Write-Log Error $error_detail $log_file
    Export-LogToAutosys $autosys_out_log
	Send-Mailtoteam 'FAILURE' $vdi_env.toupper() $log_file
    ps -id $pid|kill -force
}
Try{
    $this_path = Split-Path -Parent ($MyInvocation.MyCommand.Definition) -ea 1
}Catch{
    $this_path = [System.IO.Path]::GetDirectoryName([System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName)
}Finally{}
[void][System.Reflection.Assembly]::Load([io.file]::ReadAllBytes("$this_path\CredentialValidate.dll"))
$this_user=$env:username                                                                                   # this script running user
$this_machine=$env:computername                                                                            # this computer name
$this_domain=(Get-WmiObject Win32_ComputerSystem).Domain.Split(".")[0].ToUpper()                           # this computer domain
$log_path = $this_path.Replace('\scripts','\logs')                                                         # set log path
New-FileOrFolder $log_path -d                                                                              # create log path if it doesn't exist
$log_file="$log_path\{0:yyyyMMdd_HHmmss}_file_cleanup.log" -f (get-date)                                   # log file to store running info
$autosys_log_path="C:\Program Files\CA\Workload Automation AE\SystemAgent\WA1_AGENT\log\VDICleanup"        # initilize autosys log path
$autosys_out_log="$autosys_log_path\out.log"                                                               # normal log file path and 
$autosys_err_log="$autosys_log_path\err.log"                                                               # error log file path
$autosys_out_log,$autosys_err_log|%{New-FileOrFolder $_}                                                   # create autosys log file if it doesn't exist
$folder_size_file="$this_path\FolderSize.csv"
del $folder_size_file -force -ea 0